"""
Posnet TCP server
"""
import SocketServer, socket, time               # get socket server, handler objects
import array,sets,logging,sys,os
import select
from traceback import print_exc
import posnet_overlay_messages
import scheduler
add_to_str = lambda o : "%s %s" % (o[0],o[1])

class posnet_tcp_server(SocketServer.TCPServer):
	socket_timeout = 5 
	quit_message_type = 99
	server_request_queue_size = 7

	def __init__(self, bind_address,protocol_handler = None):
		""" be careful to ser protocol_handler with constructor or with function set_protocol_handler"""
		# network server
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		try: self.socket.bind(bind_address)
		except Exception,e: raise Exception("unable to open a socket that bind on port %s: %s"%(str(bind_address[1]),str(e)))
		self.__bind_address= self.socket.getsockname()
		self.socket.settimeout(posnet_tcp_server.socket_timeout)
		self._socket_for_select = [self.socket]
		# multi-thread and message handling
		self.__running = False
		self.__protocol_handler = protocol_handler
		# 
		self.__control_plane_scheduler = scheduler.Scheduler("posnet_control_plane")
		self.__control_plane_scheduler.add_threads(10)
		self.__query_plane_scheduler = scheduler.Scheduler("posnet_query_plane")
		self.__query_plane_scheduler.add_threads(4)
		self.logger = logging.getLogger('posnet.server')
		self.__query_plane_messages = sets.Set()
		#self.init_query_plane()
		#----------------
		# blocking part!
		self.socket.setblocking(1)
		#self.socket.setblocking(0)
		self.socket.listen(posnet_tcp_server.server_request_queue_size) # equivalent to SocketServer.TCPServer.server_activate(self)
	
	def set_protocol_handler(self,protocol_handler):  self.__protocol_handler = protocol_handler
	def sockets_for_select(self): return self._socket_for_select
		
	def init_query_plane(self):		
		self.__query_plane_messages.add( posnet_overlay_messages.posnet_message_type_query)
		self.__query_plane_messages.add( posnet_overlay_messages.posnet_message_type_ping )
		self.__query_plane_messages.add( posnet_overlay_messages.posnet_message_type_graph_request )
		self.__query_plane_messages.add( posnet_overlay_messages.posnet_message_type_state_request )
		self.__query_plane_messages.add( posnet_overlay_messages.posnet_message_type_draw )
	#-----------------------
	def fileno(self):
		"""Return socket file number.
			Interface required by select().
		"""
		return self.socket.fileno()
		 
	def stop_schedulers(self):
		""" stop schedulers """
		try: self.__control_plane_scheduler.kill()
		except: pass
		try: self.__query_plane_scheduler.kill()
		except: pass

	def serve_forever(self):
		"""Override serve_forever to handle shutdown."""
		self.__running = True
		try:
			self.logger.info('posnet_tcp_server: begin serve_forever on '+str(self.__bind_address)+", PID:"+str(os.getpid()))
			while self.__running:
				self.handle_request() 	
		finally:
			self.logger.info('posnet_tcp_server: end serve_forever')
			self.stop_schedulers()
	def shutdown(self):
		self.logger.info('posnet_tcp_server: shutdown call!')
		if self.__running:
			#####self.logger.info('end serve_forever (shutdown call)')
			self.__running = 0 

	def handle_request(self):
		"""Override handle_request to trap timeout exception."""
		try:
			#SocketServer.TCPServer.handle_request(self)
			#request, client_address = self.socket.accept()
			ready_to_read, ready_to_write, in_error	= select.select( self._socket_for_select ,[],[])
			for socket_ready in ready_to_read:
				if socket_ready == self.socket:
					# handle the server socket
					request, client_address = self.socket.accept()
					self.finish_request(request, client_address)			
				else:
					self.logger.info("###########handle requestfrom tunnel " ) 
					sys.stdout.flush()
					try:
						#request, client_address = socket_ready.accept()
						#self.logger.info("###########handle requestfrom tunnel add = %s" %client_address)a
						client_address = ("",0)
						self.finish_request(socket_ready, client_address)			
					except Exception,e:
						self.logger.error("### error while handling request in tunnel: %s"%e)
						sys.stdout.flush()
		except socket.timeout:
			self.logger.info('posnet_tcp_server: timeout')
			# The only reason for the timeout is so we can notice keyboard
			# interrupts on Win32, which don't interrupt accept() by default
			return 1
		except socket.error, e:
			self.logger.info('posnet_tcp_server: socket error '+str(e))
			return 1
		except select.error, e:
			self.logger.info('posnet_tcp_server: select error '+str(e))
			return 1
		except KeyboardInterrupt:
			self.shutdown() 

	def finish_request(self, request, client_address):
		""" """
		t0 = time.clock()
		peer_addr = request.getpeername()
		sock_addr = request.getsockname()
		buffer  = array.array("c")
		try:
			recv_ = request.recv(4)
			datasize  = array.array("I", recv_).pop()	
			#self.logger.info("Receive %d from %s"%(datasize,str(peer_addr)))
			#----------------------------------------
			while  buffer.buffer_info()[1] * buffer.itemsize  < datasize:
				wait_size = 1024
				cbuffer_len = buffer.buffer_info()[1]*buffer.itemsize
				if wait_size > datasize - cbuffer_len: wait_size = datasize - cbuffer_len 
				data = request.recv( wait_size )
				if not data: break
				buffer.extend(data)
			# treat message
			answer_type = posnet_overlay_messages.array_to_overlay_message_answer_type(buffer)
			message_type = posnet_overlay_messages.array_to_overlay_message_type( buffer )
			if message_type == posnet_tcp_server.quit_message_type:
				request.close()
				self.logger.info("quit message (distant->local) %s -> %s [%d s.]" % (add_to_str(client_address),add_to_str(sock_addr),t0) )
				self.shutdown() #shutdown network servers...
				return	
			# asynchronous answer : answer_type == 1  
			if answer_type != 0:
				request.close() #close client connection
				request = (peer_addr[0],int(answer_type))  #BEWAREEEEEE!!!
			def handle_posnet_message(): self.__protocol_handler(client_address,buffer,request)			
			# Query Plane / Control Plane
			if message_type  in self.__query_plane_messages:
				self.__query_plane_scheduler.enqueue(handle_posnet_message)
				t1 = time.clock()
				#self.logger.info("query_plane job (distant -> local) %s -> %s [%d s.]" % (add_to_str(client_address),add_to_str(sock_addr),t1-t0) )
			else:
				self.__control_plane_scheduler.enqueue(handle_posnet_message)
				t1 = time.clock()
				#self.logger.info("control_plane job (distant -> local) %s -> %s [%d s.]" % (add_to_str(client_address),add_to_str(sock_addr),t1-t0) )
		except Exception,e:
			self.logger.error("while handling request: %s (distant) -> %s (local) [%s]" % (add_to_str(client_address),add_to_str(sock_addr),str(e)) )
			print_exc()
			raise
	#------------------------------------- 
	def close_request(self, request):
		"""Called to clean up an individual request."""
		pass #print "i'm not closed :)"	

if __name__ == "__main__":
	pass
	#from posnet_overlay_messages import unpack_overlay_message
	#myPort = int(sys.argv[1])                  
	#myaddr = ( '', myPort) 
	#def handler(add,buf,client_socket):
	#	print unpack_overlay_message( buf )
	#server = posnet_tcp_server(myaddr,handler)
	#server.serve_forever()
	
